import {propPath} from './prop-func';
import {Cask} from 'cascadium';

/**
 * Rule Class
 * ----------
 * When the destination is givne, all other specficiation of next level
 * will be invalidated. The destination should be always associated to
 * an actual record.
 */
class Rule {
  constructor({patt, prop='curr.name', cast='=', action='SELF', overCast, overProp, dest}={}) {
    
    this.prop = prop;
    this.patt = patt;
    this.cast = cast;
    this.overProp = overProp;
    this.overCast = overCast;

    this.succ = new Cask();
    this.pred = undefined;

    this.dest = dest;

    this.action = action;
    this.attr = {};
  }

  moveTo(dest, {from='TAIL'}={}) {
    this.pred = dest;
    if (from === 'HEAD') {
      dest.succ.unshift(this);
    } else {
      dest.succ.push(this);
    }
  }

  match(rec, {castMethods={}, expandMethods={}, targetMethods={}, overProp, overCast} = {}, prev) {

    const self = this;
    
    // # MATCH的工作原理
    // 
    // 1. 发起match动作的是top-level rule (TLR)，从原理上TLR不拒绝任何
    //    被匹配对象，而是送往下一层的规则进行后续匹配。
    // 
    // 2. 每层会通过find来找到匹配的next-level rule (NLR)，而并非进入到
    //    下一层的备选rule列表中循环查找。
    // 
    // 3. 如果当前rule没有NLR，那么会按照当前rule中指定的destination和对
    //    应的merge operation完成分配操作。
    //
    // 4. 每层rule可以指定一个carry方法，对于级联的记录可以进行展开、筛选等
    //    操作。特别需要注意的是这个carry方法apply于cast操作之前，原因仍然
    //    是每层rule并不是在匹配自己，而是在匹配下一层，因此需要在匹配之前完
    //    成NLR所接受的形式。

    const {patt, prop, cast, dest, succ, action} = this;

    const handledProp = overProp ?? prop ?? 'curr.name';
    const handledCast = overCast ?? cast ?? '=';

    const castFunc = typeof handledCast === 'function' ? handledCast : castMethods[handledCast];

    if (castFunc === undefined) {
      throw Error(`Rule.match: casting method ${handledCast} not found in ${Object.keys(castMethods)}`);
    }

    const isMatched = castFunc(propPath(rec, handledProp), patt);

    if (isMatched) {

      // Identifying circular matching
      // 判断是否在循环匹配同一个rule。我们在匹配下一级rule时传入了上一级rule。
      // 因此此处的prev是上一级已经匹配过的rule。避免循环进入同一个rule的条件是
      // 第二次进入同一个rule时必须是匹配失败的，即isMatched必须为false，故而
      // 如果我们在此处发现了当前rule和上一个rule实为同一个对象，即意味着下次也
      // 会成功匹配这个rule，于是造成了循环匹配。
      // 
      // 通过指定overCast和overProp可以避免大多数循环匹配的情形，但是存在极少数
      // 情况，比如即使指定了overCast和overProp但仍然通过了上方的Matching，那么
      // 最好在重复使用的规则列表中，在循环匹配的这一条规则之前插入一条可以确保不会
      // 循环匹配的规则。

      if (self === prev) {
        throw Error('CIRCULAR RULE: When re-using existing sub rules, you need to avoid matching same rules by specifying overProp or overCast, or adding exception rules ahead');
      }
  
      if (succ.length === 0) {

        if (targetMethods[action] === undefined) {
          throw Error(`Rule.match: target method ${action} not found`);
        }

        targetMethods[action](dest, rec);

      } else if (expandMethods[action] === undefined) {

        throw Error (`Rule.match: expand method ${action} not found`);

      } else for (let expandedRec of expandMethods[action](rec)) {

        // Array.prototype.find iterate over the whole array
        // and stops when the first element found satisfying
        // the predicate. We take the advantage of it.

        const opts = {
          overProp: this.overProp,
          overCast: this.overCast,
          castMethods,

          expandMethods,
          targetMethods,
        }

        succ.find(subRule => subRule.match(expandedRec, opts, self));
      }

    }

    return isMatched;
  }
}

export default Rule;